4-1

除了內建函數之外,你也可以定義自己的 Perl 函數,例如:

原始檔(tsub1.pl):(灰色區域按兩下即可拷貝)
sub printVariable0 {
	print("X is equal to 10.\n");
}

&printVariable0;

在上例中,我們先定義了一個函數 printVariable0,然後再呼叫此函數,即可在螢幕印出:

X is equal to 10. 函數的定義,可以放在函數呼叫的前面或後面,並沒有一定的規定,你可以看個人喜好來安排。但必須注意的是,在呼叫函數時,一定要在函數名稱之前加上「&」。此外,Perl 的函數也可以支援遞迴呼叫(Recursive Calls),換句話說,一個函數可以呼叫其本身。

在預設情況下,所有 Perl 的變數都是全域變數(Global Variables),所以在函數中也可以引用其他變數,只要在呼叫此函數前,此變數已經被定義即可。例如:

原始檔(tsub2.pl):(灰色區域按兩下即可拷貝)
sub printVariable0 {
	print("X is equal to $X.\n");
}

$X = 20;
&printVariable0;

在上述程式碼中,$X 在函數呼叫前被定義為 20,然後我們在函數中印出此變數 $X,印出結果為:

X is equal to 20. 在函數內引用全域變數,並不是很理想,容易造成除錯上的困難。另一個比較好的辦法,則使函數有自己的引數(Arguments),因此我們可以將不同的變數送入函數來進行運算。Perl 函數的內定引數是一個名為「@_」陣列,我們可以利用此陣列來讀取送入此函數的變數值。例如,下列函數 printArgument 可以印出傳入變數的個數和值,完整程式碼(tsub3.pl)為:

原始檔(tsub3.pl):(灰色區域按兩下即可拷貝)
&printArgument(@ARGV);
sub printArgument {
	my $count=@_;
	print("$count input argument(s): ");
	for ($i=0; $i<$count; $i++) {
		print("$_[$i] ");
	}
}

在上述程式碼中,@_ 是一個陣列,因此它的第一個元素是 $_[0],第二個元素是 $_[1],依此類推。若以下列方式執行上述程式:

perl tsub3.pl 3 5 go test

則印出訊息為:

4 input argument(s): 3 5 go test 變數 @_ 對函數來說是局部變數(Local Variable),若在函數定義之外另有一個全域變數 @_,則在進入此函數時,全域變數 @_ 的值會被保留起來,所有對變數 @_ 的存取會由局部變數 @_ 所取代,直到函數結束,局部變數 @_ 消失,全域變數再出現,並恢復其原先值。

事實上,我們也可以在 Perl 函數中定義此函數的局部變數,來使得程式碼更易於瞭解,例如:

sub addTwoNumber { my($a, $b); # 定義 $a 和 $b 為局部變數 ($a, $b) = @_; # 設定 $a 和 $b 分別等於傳入的變數值 return($a + $b); # 傳回兩數相加的結果 } 在上述程式碼中,我們使用了 my 來定義 $a 和 $b 為局部變數,並設定 $a 和 $b 分別等於傳入的變數值,最後再讓函數傳回 $a 和 $b 相加的結果。此外,在上述函數中的前兩列可以合併成 my($a, $b) = @_;

我們也可以用 local 取代 my 來定義局部變數,但其有效範圍(Scope)略有差異,說明如下:

一般而言,除非有特殊需求,否則我們都使用 my 來定義局部變數。

作業:
請寫一個 Perl 程式,檢查目前目錄以下的所有檔案名稱或子目錄名稱,並印出所有不符合 DOS 8.3 檔名規格的檔案或目錄名稱。你的程式必須以遞迴的方式來檢查所有的檔案及目錄名稱。(1994年,筆者在美國工作時,要把在 UNIX 發展的 MATLAB 程式碼轉到 PC 平台,當時作業系統是 Windows 3.1,為了避免長檔名所帶來的各種困擾,因此我們就常要確認所有檔案及目錄名稱都符合 DOS 檔案的 8.3 制式規格。當時筆者即使用一個 Perl 程式來完成這一件工作,這也是筆者所寫的第一個 Perl 程式。)


Perl